﻿using Academy.Collections.Properties;
using Academy.Numerics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Academy.Collections.Generic
{
    /// <summary>
    /// Provides a base class for implementations of the <see cref="T:Academy.Collections.Generic.IMatrixArithmeticHelper`1"/> generic interface.
    /// </summary>
    /// <typeparam name="T">The type of the elements in the matrices.</typeparam>
    public abstract class MatrixArithmeticHelper<T>: IMatrixArithmeticHelper<T>
    {
        private const int MaxArraySize = 2146402440;
        private static readonly Type genType = typeof(T);
        private static volatile MatrixArithmeticHelper<T> defaultOperator;
        private static volatile MatrixArithmeticHelper<T> defaultOverLeftOperand;
        internal const string AddString = "Add";
        internal const string SubtractString = "Subtract";
        internal const string MultipString = "Multiply";

        /// <summary>
        /// Returns a default helper for <see cref="T:Academy.Collections.Generic.IMatrix`1"/> implementations 
        /// that uses the specified argument type as generic type.
        /// </summary>
        public static MatrixArithmeticHelper<T> Default
        {
            get
            {
                MatrixArithmeticHelper<T> def = defaultOperator;
                if (def == null)
                {
                    def = new DefaultHelper();
                    defaultOperator = def;
                }
                return defaultOperator;
            }
        }
        /// <summary>
        /// Returns a default helper for <see cref="T:Academy.Collections.Generic.IMatrix`1"/> implementations 
        /// that uses the specified argument type as generic type. When performs the arithmetic operations it tries 
        /// to stores the results in the left operand, which will be also the return object.
        /// </summary>
        public static MatrixArithmeticHelper<T> DefaultOverLeftOperand
        {
            get
            {
                MatrixArithmeticHelper<T> def = defaultOverLeftOperand;
                if (def == null)
                {
                    def = new LeftOperandtHelper();
                    defaultOverLeftOperand = def;
                }
                return defaultOverLeftOperand;
            }
        }

        /// <summary>
        /// Provides an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object
        /// as result value of adding two specified matrices. Uses the operator provided by
        /// the <see cref="P:Academy.Numerics.ArithmeticOperator`1.Default"/> property.
        /// </summary>
        /// <param name="left">The left matrix operand.</param>
        /// <param name="right">The right matrix operand.</param>
        /// <returns>
        /// an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object that contains the result values 
        /// of add the two specified <see cref="T:Academy.Collections.Generic.IMatrix`1"/> objects.
        /// </returns>
        public virtual IMatrix<T> Add(IMatrix<T> left, IMatrix<T> right)
        {
            return Add(left, right, ArithmeticOperator.Default);
        }
        /// <summary>
        /// When overridden in a derived class, provides an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object
        /// as result value of adding two specified matrices.
        /// </summary>
        /// <param name="left">The left matrix operand.</param>
        /// <param name="right">The right matrix operand.</param>
        /// <param name="op">The <see cref="T:Academy.Numerics.IArithmeticOperator`1"/> used to add matrix elements.</param>
        /// <returns>
        /// an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object that contains the result values 
        /// of add the two specified <see cref="T:Academy.Collections.Generic.IMatrix`1"/> objects.
        /// </returns>
        public abstract IMatrix<T> Add(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op);
        /// <summary>
        /// Provides an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object
        /// as result value of subtract two specified matrices. Uses the operator provided by
        /// the <see cref="P:Academy.Numerics.ArithmeticOperator`1.Default"/> property.
        /// </summary>
        /// <param name="left">The left matrix operand.</param>
        /// <param name="right">The right matrix operand.</param>
        /// <returns>
        /// an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object that contains the result values 
        /// of Subtract the two specified <see cref="T:Academy.Collections.Generic.IMatrix`1"/> objects.
        /// </returns>
        public virtual IMatrix<T> Subtract(IMatrix<T> left, IMatrix<T> right)
        {
            return Subtract(left, right, ArithmeticOperator.Default);
        }
        /// <summary>
        /// When overridden in a derived class, provides an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object
        /// as result value of subtract two specified matrices.
        /// </summary>
        /// <param name="left">The left matrix operand.</param>
        /// <param name="right">The right matrix operand.</param>
        /// <param name="op">The <see cref="T:Academy.Numerics.IArithmeticOperator`1"/> used to Subtract matrix elements.</param>
        /// <returns>
        /// an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object that contains the result values 
        /// of Subtract the two specified <see cref="T:Academy.Collections.Generic.IMatrix`1"/> objects.
        /// </returns>
        public abstract IMatrix<T> Subtract(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op);
        /// <summary>
        /// Provides an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object
        /// as result value of multiply two specified matrices. Uses the operator provided by
        /// the <see cref="P:Academy.Numerics.ArithmeticOperator`1.Default"/> property.
        /// </summary>
        /// <param name="left">The left matrix operand.</param>
        /// <param name="right">The right matrix operand.</param>
        /// <returns>
        /// an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object that contains the result values 
        /// of multiply the two specified <see cref="T:Academy.Collections.Generic.IMatrix`1"/> objects.
        /// </returns>
        public virtual IMatrix<T> Multiply(IMatrix<T> left, IMatrix<T> right)
        {
            return Multiply(left, right, ArithmeticOperator.Default);
        }
        /// <summary>
        /// When overridden in a derived class, provides an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object
        /// as result value of multiply two specified matrices.
        /// </summary>
        /// <param name="left">The left matrix operand.</param>
        /// <param name="right">The right matrix operand.</param>
        /// <param name="op">The <see cref="T:Academy.Numerics.IArithmeticOperator`1"/> used to Subtract matrix elements.</param>
        /// <returns>
        /// an <see cref="T:Academy.Collections.Generic.IMatrix`1"/> object that contains the result values 
        /// of multiply the two specified <see cref="T:Academy.Collections.Generic.IMatrix`1"/> objects.
        /// </returns>
        public abstract IMatrix<T> Multiply(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op);
        
        #region Inner Implementations
        private class DefaultHelper : MatrixArithmeticHelper<T>
        {
            public override IMatrix<T> Add(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op)
            {
                if (left == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.left);
                }
                if (right == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.right);
                }
                if (op == null)
                {
                    op = ArithmeticOperator.Default;
                }
                if ((left.RowCount != right.RowCount) || (left.ColumnCount != right.ColumnCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsNotMatches);
                }
                long value = (long)left.RowCount * (long)right.ColumnCount;
                if (value > MaxArraySize)
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsTooBig);
                }
                Matrix<T> matrix = new Matrix<T>(left.RowCount, right.ColumnCount);
                for (int i = 0; i < matrix.RowCount; i++)
                {
                    for (int j = 0; j < matrix.ColumnCount; j++)
                    {
                        object obj = op.Add(left[i, j], right[i, j]);
                        Type objType = obj.GetType();
                        if (!objType.IsAssignableFrom(genType))
                        {
                            string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, objType, AddString);
                            Thrower.InvalidCastException(message);
                        }
                        matrix[i, j] = (T)obj;
                    }
                }
                return matrix;
            }
            public override IMatrix<T> Subtract(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op)
            {
                if (left == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.left);
                }
                if (right == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.right);
                }
                if (op == null)
                {
                    op = ArithmeticOperator.Default;
                }
                if ((left.RowCount != right.RowCount) || (left.ColumnCount != right.ColumnCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsNotMatches);
                }
                long value = (long)left.RowCount * (long)right.ColumnCount;
                if (value > MaxArraySize)
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsTooBig);
                }
                Matrix<T> matrix = new Matrix<T>(left.RowCount, right.ColumnCount);
                for (int i = 0; i < matrix.RowCount; i++)
                {
                    for (int j = 0; j < matrix.ColumnCount; j++)
                    {
                        object obj = op.Subtract(left[i, j], right[i, j]);
                        Type objType = obj.GetType();
                        if (!objType.IsAssignableFrom(genType))
                        {
                            string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, objType, SubtractString);
                            Thrower.InvalidCastException(message);
                        }
                        matrix[i, j] = (T)obj;
                    }
                }
                return matrix;
            }
            public override IMatrix<T> Multiply(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op)
            {
                if (left == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.left);
                }
                if (right == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.right);
                }
                if (op == null)
                {
                    op = ArithmeticOperator.Default;
                }
                if ((left.RowCount != right.ColumnCount) || (left.ColumnCount != right.RowCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MultiplicityDimensionsNotMatch);
                }
                int rowCount = left.RowCount;
                int colCount = right.ColumnCount;
                long value = (long)rowCount * (long)rowCount;
                if (value > MaxArraySize)
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsTooBig);
                }
                Matrix<T> matrix = new Matrix<T>(rowCount, colCount);
                for (int i = 0; i < left.RowCount; i++)
                {
                    for (int j = 0; j < right.ColumnCount; j++)
                    {
                        T acc = default(T);
                        for (int k = 0; k < right.RowCount; k++)
                        {
                            object obj1 = op.Multiply(left[i, k], right[k, j]);
                            Type obj1Type = obj1.GetType();
                            if (!obj1Type.IsAssignableFrom(genType))
                            {
                                string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, obj1Type, MultipString);
                                Thrower.InvalidCastException(message);
                            }
                            object obj2 = op.Add(acc, obj1);
                            Type obj2Type = obj1.GetType();
                            if (!obj2Type.IsAssignableFrom(genType))
                            {
                                string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, obj2Type, MultipString);
                                Thrower.InvalidCastException(message);
                            }
                            acc = (T)obj2;
                        }
                        matrix[i, j] = acc;
                    }
                }
                return matrix;
            }
        }
        private class LeftOperandtHelper : MatrixArithmeticHelper<T>
        {
            public override IMatrix<T> Add(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op)
            {
                if (left == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.left);
                }
                if (right == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.right);
                }
                if (op == null)
                {
                    op = ArithmeticOperator.Default;
                }
                if ((left.RowCount != right.RowCount) || (left.ColumnCount != right.ColumnCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsNotMatches);
                }
                for (int i = 0; i < left.RowCount; i++)
                {
                    for (int j = 0; j < left.ColumnCount; j++)
                    {
                        object obj = op.Add(left[i, j], right[i, j]);
                        Type objType = obj.GetType();
                        if (!objType.IsAssignableFrom(genType))
                        {
                            string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, objType, AddString);
                            Thrower.InvalidCastException(message);
                        }
                        try
                        {
                            left[i, j] = (T)obj;
                        }
                        catch (Exception)
                        {
                            string message = string.Format(Resources.InvalidOperation_MatrixNotAccessible_G, i, j);
                            Thrower.InvalidOperationException(message);
                        }
                    }
                }
                return left;
            }
            public override IMatrix<T> Subtract(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op)
            {
                if (left == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.left);
                }
                if (right == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.right);
                }
                if (op == null)
                {
                    op = ArithmeticOperator.Default;
                }
                if ((left.RowCount != right.RowCount) || (left.ColumnCount != right.ColumnCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionsNotMatches);
                }
                for (int i = 0; i < left.RowCount; i++)
                {
                    for (int j = 0; j < left.ColumnCount; j++)
                    {
                        object obj = op.Subtract(left[i, j], right[i, j]);
                        Type objType = obj.GetType();
                        if (!objType.IsAssignableFrom(genType))
                        {
                            string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, objType, SubtractString);
                            Thrower.InvalidCastException(message);
                        }
                        try
                        {
                            left[i, j] = (T)obj;
                        }
                        catch (Exception)
                        {
                            string message = string.Format(Resources.InvalidOperation_MatrixNotAccessible_G, i, j);
                            Thrower.InvalidOperationException(message);
                        }
                    }
                }
                return left;
            }
            public override IMatrix<T> Multiply(IMatrix<T> left, IMatrix<T> right, IArithmeticOperator op)
            {
                if (left == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.left);
                }
                if (right == null)
                {
                    Thrower.ArgumentNullException(ArgumentType.right);
                }
                if (op == null)
                {
                    op = ArithmeticOperator.Default;
                }
                if ((left.RowCount != right.ColumnCount) || (left.ColumnCount != right.RowCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MultiplicityDimensionsNotMatch);
                }
                if ((left.RowCount != left.ColumnCount))
                {
                    Thrower.InvalidOperationException(Resources.InvalidOperation_MatrixDimensionInvalid);
                }
                int rowCount = left.RowCount;
                int colCount = left.ColumnCount;
                T[] array = new T[rowCount];

                for (int i = 0; i < left.RowCount; i++)
                {
                    for (int j = 0; j < right.ColumnCount; j++)
                    {
                        T acc = default(T);
                        for (int k = 0; k < right.RowCount; k++)
                        {
                            object obj1 = op.Multiply(left[i, k], right[k, j]);
                            Type obj1Type = obj1.GetType();
                            if (!obj1Type.IsAssignableFrom(genType))
                            {
                                string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, obj1Type, MultipString);
                                Thrower.InvalidCastException(message);
                            }
                            object obj2 = op.Add(acc, obj1);
                            Type obj2Type = obj1.GetType();
                            if (!obj2Type.IsAssignableFrom(genType))
                            {
                                string message = string.Format(Resources.InvalidCast_ReturnTypeNotCompatible_G, genType, obj2Type, MultipString);
                                Thrower.InvalidCastException(message);
                            }
                            acc = (T)obj2;
                            array[j] = acc;
                        }
                    }
                    for (int m = 0; m < colCount; m++)
                    {
                        try
                        {
                            left[i, m] = array[m];
                        }
                        catch (Exception)
                        {
                            string message = string.Format(Resources.InvalidOperation_MatrixNotAccessible_G, i, m);
                            Thrower.InvalidOperationException(message);
                        }
                    }
                }
                return left;
            }
        } 
        #endregion
    }
}
